﻿using System;
using System.Collections.Generic;
using System.IO;
using Aeon.Emulator.Memory;
using Aeon.Emulator.Interrupts;

namespace Aeon.Emulator.Video
{
    /// <summary>
    /// Provides emulated video and int 10h functions.
    /// </summary>
    internal sealed class VideoHandler : IInterruptHandler, IInputPort, IOutputPort
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the VideoHandler class.
        /// </summary>
        /// <param name="vm">Virtual machine instance associated with this instance.</param>
        public VideoHandler(VirtualMachine vm)
        {
            this.vm = vm;
            this.nativeMemory = new NativeMemory(TotalVramBytes);
            this.videoRam = this.nativeMemory.Pointer;
            this.vbe = new Vesa.VbeHandler(this);
            this.graphics = new Graphics(this.videoHeap);
            this.sequencer = new Sequencer();

            InitializeStaticFunctionalityTable();

            textDisplay = new TextConsole(this, vm.PhysicalMemory.Bios);
            SetDisplayMode(VideoMode10.ColorText80x25x4);
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the current display mode.
        /// </summary>
        public VideoMode CurrentMode { get; private set; }
        /// <summary>
        /// Gets the text-mode display instance.
        /// </summary>
        public TextConsole TextConsole
        {
            get { return textDisplay; }
        }
        /// <summary>
        /// Gets the VGA DAC.
        /// </summary>
        public Dac Dac
        {
            get { return dac; }
        }
        /// <summary>
        /// Gets the VGA attribute controller.
        /// </summary>
        public AttributeController AttributeController
        {
            get { return attributeController; }
        }
        /// <summary>
        /// Gets the VGA graphics controller.
        /// </summary>
        public Graphics Graphics
        {
            get { return graphics; }
        }
        /// <summary>
        /// Gets the VGA sequencer.
        /// </summary>
        public Sequencer Sequencer
        {
            get { return sequencer; }
        }
        /// <summary>
        /// Gets the VGA CRT controller.
        /// </summary>
        public CrtController CrtController
        {
            get { return crtController; }
        }
        /// <summary>
        /// Gets a pointer to the emulated video RAM.
        /// </summary>
        public IntPtr VideoRam
        {
            get { return videoRam; }
        }
        /// <summary>
        /// Gets the virtual machine instance which owns the VideoHandler.
        /// </summary>
        public VirtualMachine VirtualMachine
        {
            get { return vm; }
        }
        #endregion

        #region IInterruptHandler Members
        IEnumerable<InterruptHandlerInfo> IInterruptHandler.HandledInterrupts
        {
            get { return new InterruptHandlerInfo[] { 0x10 }; }
        }
        void IInterruptHandler.HandleInterrupt(int interrupt)
        {
            switch(vm.Processor.AH)
            {
            case 0x4F:
                this.vbe.HandleFunction();
                break;

            case Functions.GetDisplayMode:
                vm.Processor.AH = vm.PhysicalMemory.Bios.ScreenColumns;
                vm.Processor.AL = (byte)vm.PhysicalMemory.Bios.VideoMode;
                break;

            case Functions.ScrollUpWindow:
                //if(vm.Processor.AL == 0)
                //    textDisplay.Clear();
                //else
                {
                    byte foreground = (byte)((vm.Processor.BX >> 8) & 0x0F);
                    byte background = (byte)((vm.Processor.BX >> 12) & 0x0F);
                    textDisplay.ScrollTextUp(vm.Processor.CL, vm.Processor.CH, vm.Processor.DL, vm.Processor.DH, vm.Processor.AL, foreground, background);
                }
                break;

            case Functions.EGA:
                switch(vm.Processor.BL)
                {
                case Functions.EGA_GetInfo:
                    vm.Processor.BX = 0x03; // 256k installed
                    vm.Processor.CX = 0x09; // EGA switches set
                    break;

                case Functions.EGA_SelectVerticalResolution:
                    if(vm.Processor.AL == 0)
                        this.verticalTextResolution = 8;
                    else if(vm.Processor.AL == 1)
                        this.verticalTextResolution = 14;
                    else
                        this.verticalTextResolution = 16;
                    vm.Processor.AL = 0x12; // Success
                    break;

                case Functions.EGA_PaletteLoading:
                    this.defaultPaletteLoading = vm.Processor.AL == 0;
                    break;

                default:
                    System.Diagnostics.Debug.WriteLine(string.Format("Video command {0:X2}, BL={1:X2}h not implemented.", Functions.EGA, vm.Processor.BL));
                    break;
                }
                break;

            case Functions.ReadCharacterAndAttributeAtCursor:
                vm.Processor.AX = (short)textDisplay.GetCharacter(textDisplay.CursorPosition.X, textDisplay.CursorPosition.Y);
                break;

            case Functions.WriteCharacterAndAttributeAtCursor:
                TextConsole.Write(vm.Processor.AL, (byte)(vm.Processor.BL & 0x0F), (byte)(vm.Processor.BL >> 4), false);
                break;

            case Functions.WriteCharacterAtCursor:
                TextConsole.Write(vm.Processor.AL);
                break;

            case Functions.GetDisplayCombinationCode:
                vm.Processor.AL = 0x1A;
                vm.Processor.BX = 0x0008;
                break;

            case Functions.SetDisplayMode:
                SetDisplayMode((VideoMode10)vm.Processor.AL);
                break;

            case Functions.SetCursorPosition:
                textDisplay.CursorPosition = new Point(vm.Processor.DL, vm.Processor.DH);
                break;

            case Functions.SetCursorShape:
                SetCursorShape(vm.Processor.CH, vm.Processor.CL);
                break;

            case Functions.SelectActiveDisplayPage:
                this.CurrentMode.ActiveDisplayPage = vm.Processor.AL;
                break;

            case Functions.Palette:
                switch(vm.Processor.AL)
                {
                case Functions.Palette_SetSingleRegister:
                    SetEgaPaletteRegister(vm.Processor.BL, vm.Processor.BH);
                    break;

                case Functions.Palette_SetBorderColor:
                    // Ignore for now.
                    break;

                case Functions.Palette_SetAllRegisters:
                    SetAllEgaPaletteRegisters();
                    break;

                case Functions.Palette_ReadSingleDacRegister:
                    // These are commented out because they cause weird issues sometimes.
                    //vm.Processor.DH = (byte)((dac.Palette[vm.Processor.BL] >> 18) & 0xCF);
                    //vm.Processor.CH = (byte)((dac.Palette[vm.Processor.BL] >> 10) & 0xCF);
                    //vm.Processor.CL = (byte)((dac.Palette[vm.Processor.BL] >> 2) & 0xCF);
                    break;

                case Functions.Palette_SetSingleDacRegister:
                    dac.SetColor(vm.Processor.BL, vm.Processor.DH, vm.Processor.CH, vm.Processor.CL);
                    break;

                case Functions.Palette_SetDacRegisters:
                    SetDacRegisters();
                    break;

                case Functions.Palette_ReadDacRegisters:
                    ReadDacRegisters();
                    break;

                case Functions.Palette_ToggleBlink:
                    // Blinking is not emulated.
                    break;

                case Functions.Palette_SelectDacColorPage:
                    System.Diagnostics.Debug.WriteLine("Select DAC color page");
                    break;

                default:
                    throw new NotImplementedException(string.Format("Video command 10{0:X2}h not implemented.", vm.Processor.AL));
                }
                break;

            case Functions.GetCursorPosition:
                vm.Processor.CH = 14;
                vm.Processor.CL = 15;
                vm.Processor.DH = (byte)TextConsole.CursorPosition.Y;
                vm.Processor.DL = (byte)TextConsole.CursorPosition.X;
                break;

            case Functions.TeletypeOutput:
                TextConsole.Write(vm.Processor.AL);
                break;

            case Functions.GetFunctionalityInfo:
                GetFunctionalityInfo();
                break;

            case 0xEF:
                vm.Processor.DX = -1;
                break;

            case 0xFE:
                break;

            case Functions.Font:
                switch(vm.Processor.AL)
                {
                case Functions.Font_GetFontInfo:
                    GetFontInfo();
                    break;

                case Functions.Font_Load8x8:
                    SwitchTo80x50TextMode();
                    break;

                case Functions.Font_Load8x16:
                    break;

                default:
                    throw new NotImplementedException(string.Format("Video command 11{0:X2}h not implemented.", vm.Processor.AL));
                }
                break;
                
            default:
                System.Diagnostics.Debug.WriteLine(string.Format("Video command {0:X2}h not implemented.", vm.Processor.AH));
                break;
            }
        }
        #endregion

        #region IInputPort Members
        IEnumerable<int> IInputPort.InputPorts
        {
            get
            {
                HashSet<int> ports = new HashSet<int>();
                ports.Add(Ports.AttributeAddress);
                ports.Add(Ports.AttributeData);
                ports.Add(Ports.CrtControllerAddress);
                ports.Add(Ports.CrtControllerAddressAlt);
                ports.Add(Ports.CrtControllerData);
                ports.Add(Ports.CrtControllerDataAlt);
                ports.Add(Ports.DacAddressReadMode);
                ports.Add(Ports.DacAddressWriteMode);
                ports.Add(Ports.DacData);
                ports.Add(Ports.DacStateRead);
                ports.Add(Ports.FeatureControlRead);
                ports.Add(Ports.GraphicsControllerAddress);
                ports.Add(Ports.GraphicsControllerData);
                ports.Add(Ports.InputStatus0Read);
                ports.Add(Ports.InputStatus1Read);
                ports.Add(Ports.InputStatus1ReadAlt);
                ports.Add(Ports.MiscOutputRead);
                ports.Add(Ports.SequencerAddress);
                ports.Add(Ports.SequencerData);
                return ports;
            }
        }
        public byte ReadByte(int port)
        {
            switch(port)
            {
            case Ports.DacAddressReadMode:
                return dac.ReadIndex;

            case Ports.DacAddressWriteMode:
                return dac.WriteIndex;

            case Ports.DacData:
                return dac.Read();

            case Ports.GraphicsControllerAddress:
                return (byte)graphicsRegister;

            case Ports.GraphicsControllerData:
                return graphics.ReadRegister(graphicsRegister);

            case Ports.SequencerAddress:
                return (byte)sequencerRegister;

            case Ports.SequencerData:
                return sequencer.ReadRegister(sequencerRegister);

            case Ports.AttributeAddress:
                return (byte)attributeRegister;

            case Ports.AttributeData:
                return attributeController.ReadRegister(attributeRegister);

            case Ports.CrtControllerAddress:
            case Ports.CrtControllerAddressAlt:
                return (byte)crtRegister;

            case Ports.CrtControllerData:
            case Ports.CrtControllerDataAlt:
                return crtController.ReadRegister(crtRegister);

            case Ports.InputStatus1Read:
            case Ports.InputStatus1ReadAlt:
                attributeDataMode = false;
                return GetInputStatus1Value();

            default:
                return 0;
            }
        }
        public ushort ReadWord(int port)
        {
            return ReadByte(port);
        }
        #endregion

        #region IOutputPort Members
        IEnumerable<int> IOutputPort.OutputPorts
        {
            get
            {
                HashSet<int> ports = new HashSet<int>();
                ports.Add(Ports.AttributeAddress);
                ports.Add(Ports.AttributeData);
                ports.Add(Ports.CrtControllerAddress);
                ports.Add(Ports.CrtControllerAddressAlt);
                ports.Add(Ports.CrtControllerData);
                ports.Add(Ports.CrtControllerDataAlt);
                ports.Add(Ports.DacAddressReadMode);
                ports.Add(Ports.DacAddressWriteMode);
                ports.Add(Ports.DacData);
                ports.Add(Ports.FeatureControlWrite);
                ports.Add(Ports.FeatureControlWriteAlt);
                ports.Add(Ports.GraphicsControllerAddress);
                ports.Add(Ports.GraphicsControllerData);
                ports.Add(Ports.MiscOutputWrite);
                ports.Add(Ports.SequencerAddress);
                ports.Add(Ports.SequencerData);
                return ports;
            }
        }
        public void WriteByte(int port, byte value)
        {
            switch(port)
            {
            case Ports.DacAddressReadMode:
                dac.ReadIndex = value;
                break;

            case Ports.DacAddressWriteMode:
                dac.WriteIndex = value;
                break;

            case Ports.DacData:
                dac.Write(value);
                break;

            case Ports.GraphicsControllerAddress:
                graphicsRegister = (GraphicsRegister)value;
                break;

            case Ports.GraphicsControllerData:
                graphics.WriteRegister(graphicsRegister, value);
                break;

            case Ports.SequencerAddress:
                sequencerRegister = (SequencerRegister)value;
                break;

            case Ports.SequencerData:
                var previousMode = sequencer.SequencerMemoryMode;
                sequencer.WriteRegister(sequencerRegister, value);
                if((previousMode & SequencerMemoryMode.Chain4) == SequencerMemoryMode.Chain4 && (sequencer.SequencerMemoryMode & SequencerMemoryMode.Chain4) == 0)
                    EnterModeX();
                break;

            case Ports.AttributeAddress:
                if(!attributeDataMode)
                    attributeRegister = (AttributeControllerRegister)(value & 0x1F);
                else
                    attributeController.WriteRegister(attributeRegister, value);
                attributeDataMode = !attributeDataMode;
                break;

            case Ports.AttributeData:
                attributeController.WriteRegister(attributeRegister, value);
                break;

            case Ports.CrtControllerAddress:
            case Ports.CrtControllerAddressAlt:
                crtRegister = (CrtControllerRegister)value;
                break;

            case Ports.CrtControllerData:
            case Ports.CrtControllerDataAlt:
                int previousVerticalEnd = crtController.VerticalDisplayEnd;
                crtController.WriteRegister(crtRegister, value);
                if(previousVerticalEnd != crtController.VerticalDisplayEnd)
                    ChangeVerticalEnd();
                break;
            }
        }
        public void WriteWord(int port, ushort value)
        {
            WriteByte(port, (byte)value);
            WriteByte(port + 1, (byte)(value >> 8));
        }
        #endregion

        #region IVirtualDevice Members
        void IVirtualDevice.Pause()
        {
        }
        void IVirtualDevice.Resume()
        {
        }
        /// <summary>
        /// Invoked when the virtual device has been added to a VirtualMachine.
        /// </summary>
        /// <param name="vm">VirtualMachine which owns the device.</param>
        void IVirtualDevice.DeviceRegistered(VirtualMachine vm)
        {
            vm.RegisterVirtualDevice(this.vbe);
        }
        /// <summary>
        /// Invoked when the device should save its current state to a <see cref="System.IO.Stream"/>.
        /// </summary>
        /// <param name="destination"><see cref="System.IO.Stream"/> to which the state is saved.</param>
        void IVirtualDevice.SaveCurrentState(Stream destination)
        {
        }
        /// <summary>
        /// Invoked when the device should restore its state from a <see cref="System.IO.Stream"/>.
        /// </summary>
        /// <param name="source"><see cref="System.IO.Stream"/> from which the state is restored.</param>
        void IVirtualDevice.RestoreState(Stream source)
        {
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Reads a byte from video RAM.
        /// </summary>
        /// <param name="offset">Offset of byte to read.</param>
        /// <returns>Byte read from video RAM.</returns>
        public byte GetVramByte(uint offset)
        {
            return CurrentMode.GetVramByte(offset);
        }
        /// <summary>
        /// Sets a byte in video RAM to a specified value.
        /// </summary>
        /// <param name="offset">Offset of byte to set.</param>
        /// <param name="value">Value to write.</param>
        public void SetVramByte(uint offset, byte value)
        {
            CurrentMode.SetVramByte(offset, value);
        }
        /// <summary>
        /// Reads a word from video RAM.
        /// </summary>
        /// <param name="offset">Offset of word to read.</param>
        /// <returns>Word read from video RAM.</returns>
        public ushort GetVramWord(uint offset)
        {
            return CurrentMode.GetVramWord(offset);
        }
        /// <summary>
        /// Sets a word in video RAM to a specified value.
        /// </summary>
        /// <param name="offset">Offset of word to set.</param>
        /// <param name="value">Value to write.</param>
        public void SetVramWord(uint offset, ushort value)
        {
            CurrentMode.SetVramWord(offset, value);
        }
        /// <summary>
        /// Reads a doubleword from video RAM.
        /// </summary>
        /// <param name="offset">Offset of doubleword to read.</param>
        /// <returns>Doubleword read from video RAM.</returns>
        public uint GetVramDWord(uint offset)
        {
            return CurrentMode.GetVramDWord(offset);
        }
        /// <summary>
        /// Sets a doubleword in video RAM to a specified value.
        /// </summary>
        /// <param name="offset">Offset of doubleword to set.</param>
        /// <param name="value">Value to write.</param>
        public void SetVramDWord(uint offset, uint value)
        {
            CurrentMode.SetVramDWord(offset, value);
        }
        /// <summary>
        /// Initializes a new display mode.
        /// </summary>
        /// <param name="videoMode">New display mode.</param>
        public void SetDisplayMode(VideoMode10 videoMode)
        {
            vm.PhysicalMemory.Bios.VideoMode = videoMode;
            VideoMode mode;

            switch(videoMode)
            {
            case VideoMode10.ColorText40x25x4:
                mode = new Modes.TextMode(40, 25, 8, this);
                break;

            case VideoMode10.ColorText80x25x4:
                mode = new Modes.TextMode(80, 25, this.verticalTextResolution, this);
                break;

            case VideoMode10.ColorGraphics320x200x2A:
            case VideoMode10.ColorGraphics320x200x2B:
                throw new NotImplementedException("CGA graphics not implemented.");

            case VideoMode10.ColorGraphics320x200x4:
                mode = new Modes.EgaVga16(320, 200, 8, this);
                break;

            case VideoMode10.ColorGraphics640x200x4:
                mode = new Modes.EgaVga16(640, 200, 8, this);
                break;

            case VideoMode10.ColorGraphics640x350x4:
                mode = new Modes.EgaVga16(640, 350, 8, this);
                break;

            case VideoMode10.Graphics640x480x4:
                mode = new Modes.EgaVga16(640, 480, 16, this);
                break;

            case VideoMode10.Graphics320x200x8:
                sequencer.SequencerMemoryMode = SequencerMemoryMode.Chain4;
                mode = new Modes.Vga256(320, 200, this);
                break;

            default:
                throw new NotSupportedException();
            }

            SetDisplayMode(mode);
        }
        /// <summary>
        /// Initializes a new display mode.
        /// </summary>
        /// <param name="mode">New display mode.</param>
        public void SetDisplayMode(VideoMode mode)
        {
            this.CurrentMode = mode;
            mode.InitializeMode(this);
            graphics.WriteRegister(GraphicsRegister.ColorDontCare, 0x0F);

            if(this.defaultPaletteLoading)
                dac.Reset();

            vm.OnVideoModeChanged(EventArgs.Empty);
        }
        /// <summary>
        /// Releases resources used by the VideoHandler instance.
        /// </summary>
        public void Dispose()
        {
            this.nativeMemory.Dispose();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Sets the current mode to unchained mode 13h.
        /// </summary>
        private void EnterModeX()
        {
            var mode = new Modes.Unchained256(320, 200, this);
            crtController.Offset = 320 / 8;
            this.CurrentMode = mode;
            vm.OnVideoModeChanged(EventArgs.Empty);
        }
        /// <summary>
        /// Changes the current video mode to match the new value of the vertical end register.
        /// </summary>
        private void ChangeVerticalEnd()
        {
            int newEnd = 624 - this.crtController.VerticalDisplayEnd | ((this.crtController.Overflow & (1 << 1)) << 7) | ((this.crtController.Overflow & (1 << 6)) << 3);
            this.CurrentMode.Height = newEnd;
            vm.OnVideoModeChanged(EventArgs.Empty);
        }
        /// <summary>
        /// Sets the current mode to text mode 80x50.
        /// </summary>
        private void SwitchTo80x50TextMode()
        {
            var mode = new Modes.TextMode(80, 50, 8, this);
            this.CurrentMode = mode;
            vm.OnVideoModeChanged(EventArgs.Empty);
        }
        /// <summary>
        /// Sets DAC color registers to values in emulated RAM.
        /// </summary>
        private void SetDacRegisters()
        {
            ushort segment = vm.Processor.ES;
            uint offset = (ushort)vm.Processor.DX;
            int start = vm.Processor.BX;
            int count = vm.Processor.CX;

            for(int i = start; i < count; i++)
            {
                byte r = vm.PhysicalMemory.GetByte(segment, offset);
                byte g = vm.PhysicalMemory.GetByte(segment, offset + 1u);
                byte b = vm.PhysicalMemory.GetByte(segment, offset + 2u);

                dac.SetColor((byte)(start + i), r, g, b);
                
                offset += 3u;
            }
        }
        /// <summary>
        /// Reads DAC color registers to emulated RAM.
        /// </summary>
        private void ReadDacRegisters()
        {
            ushort segment = vm.Processor.ES;
            uint offset = (ushort)vm.Processor.DX;
            int start = vm.Processor.BX;
            int count = vm.Processor.CX;

            for(int i = start; i < count; i++)
            {
                uint r = (dac.Palette[start + i] >> 18) & 0xCFu;
                uint g = (dac.Palette[start + i] >> 10) & 0xCFu;
                uint b = (dac.Palette[start + i] >> 2) & 0xCFu;

                vm.PhysicalMemory.SetByte(segment, offset, (byte)r);
                vm.PhysicalMemory.SetByte(segment, offset + 1u, (byte)g);
                vm.PhysicalMemory.SetByte(segment, offset + 2u, (byte)b);

                offset += 3u;
            }
        }
        /// <summary>
        /// Sets all of the EGA color palette registers to values in emulated RAM.
        /// </summary>
        private void SetAllEgaPaletteRegisters()
        {
            ushort segment = vm.Processor.ES;
            uint offset = (ushort)vm.Processor.DX;

            for(uint i = 0; i < 16u; i++)
                SetEgaPaletteRegister((int)i, vm.PhysicalMemory.GetByte(segment, offset + i));
        }
        /// <summary>
        /// Gets a specific EGA color palette register.
        /// </summary>
        /// <param name="index">Index of color to set.</param>
        /// <param name="color">New value of the color.</param>
        private void SetEgaPaletteRegister(int index, byte color)
        {
            if(vm.PhysicalMemory.Bios.VideoMode == VideoMode10.ColorGraphics320x200x4)
                attributeController.InternalPalette[index & 0x0F] = (byte)(color & 0x0F);
            else
                attributeController.InternalPalette[index & 0x0F] = color;
        }
        /// <summary>
        /// Gets information about BIOS fonts.
        /// </summary>
        private void GetFontInfo()
        {
            RealModeAddress address;

            switch(vm.Processor.BH)
            {
            case 0x00:
                address = vm.PhysicalMemory.GetRealModeInterruptAddress(0x1F);
                break;

            case 0x01:
                address = vm.PhysicalMemory.GetRealModeInterruptAddress(0x43);
                break;

            case 0x02:
            case 0x05:
                address = new RealModeAddress(PhysicalMemory.FontSegment, PhysicalMemory.Font8x14Offset);
                break;

            case 0x03:
                address = new RealModeAddress(PhysicalMemory.FontSegment, PhysicalMemory.Font8x8Offset);
                break;

            case 0x04:
                address = new RealModeAddress(PhysicalMemory.FontSegment, PhysicalMemory.Font8x8Offset + 128 * 8);
                break;

            case 0x06:
            case 0x07:
            default:
                address = new RealModeAddress(PhysicalMemory.FontSegment, PhysicalMemory.Font8x16Offset);
                break;
            }

            vm.WriteSegmentRegister(SegmentIndex.ES, address.Segment);
            vm.Processor.BP = address.Offset;
            vm.Processor.CX = (short)this.CurrentMode.FontHeight;
            vm.Processor.DL = (byte)vm.PhysicalMemory.Bios.ScreenRows;
        }
        /// <summary>
        /// Changes the appearance of the text-mode cursor.
        /// </summary>
        /// <param name="topOptions">Top scan line and options.</param>
        /// <param name="bottom">Bottom scan line.</param>
        private void SetCursorShape(int topOptions, int bottom)
        {
            int mode = (topOptions >> 4) & 3;
            vm.IsCursorVisible = mode != 2;
        }
        /// <summary>
        /// Writes values to the static functionality table in emulated memory.
        /// </summary>
        private void InitializeStaticFunctionalityTable()
        {
            var memory = vm.PhysicalMemory;
            memory.SetUInt32(StaticFunctionalityTableSegment, 0, 0x000FFFFF); // supports all video modes
            memory.SetByte(StaticFunctionalityTableSegment, 0x07, 0x07); // supports all scanlines
        }
        /// <summary>
        /// Writes a table of information about the current video mode.
        /// </summary>
        private void GetFunctionalityInfo()
        {
            ushort segment = vm.Processor.ES;
            ushort offset = vm.Processor.DI;

            var memory = vm.PhysicalMemory;
            var bios = memory.Bios;

            var cursorPos = textDisplay.CursorPosition;

            memory.SetUInt32(segment, offset, StaticFunctionalityTableSegment << 16); // SFT address
            memory.SetByte(segment, offset + 0x04u, (byte)bios.VideoMode); // video mode
            memory.SetUInt16(segment, offset + 0x05u, bios.ScreenColumns); // columns
            memory.SetUInt32(segment, offset + 0x07u, 0); // regen buffer
            for(uint i = 0; i < 8; i++)
            {
                memory.SetByte(segment, offset + 0x0Bu + i * 2u, (byte)cursorPos.X); // text cursor x
                memory.SetByte(segment, offset + 0x0Cu + i * 2u, (byte)cursorPos.Y); // text cursor y
            }

            memory.SetUInt16(segment, offset + 0x1Bu, 0); // cursor type
            memory.SetByte(segment, offset + 0x1Du, (byte)this.CurrentMode.ActiveDisplayPage); // active display page
            memory.SetUInt16(segment, offset + 0x1Eu, bios.CrtControllerBaseAddress); // CRTC base address
            memory.SetByte(segment, offset + 0x20u, 0); // current value of port 3x8h
            memory.SetByte(segment, offset + 0x21u, 0); // current value of port 3x9h
            memory.SetByte(segment, offset + 0x22u, bios.ScreenRows); // screen rows
            memory.SetUInt16(segment, offset + 0x23u, (ushort)this.CurrentMode.FontHeight); // bytes per character
            memory.SetByte(segment, offset + 0x25u, (byte)bios.VideoMode); // active display combination code
            memory.SetByte(segment, offset + 0x26u, (byte)bios.VideoMode); // alternate display combination code
            memory.SetUInt16(segment, offset + 0x27u, (ushort)(this.CurrentMode.BitsPerPixel * 8)); // number of colors supported in current mode
            memory.SetByte(segment, offset + 0x29u, 4); // number of pages
            memory.SetByte(segment, offset + 0x2Au, 0); // number of active scanlines

            // Indicate success.
            vm.Processor.AL = 0x1B;
        }
        #endregion

        #region Private Static Methods
        /// <summary>
        /// Returns the current value of the input status 1 register.
        /// </summary>
        /// <returns>Current value of the input status 1 register.</returns>
        private static byte GetInputStatus1Value()
        {
            uint value = InterruptTimer.IsInRealtimeInterval(VerticalBlankingTime, RefreshRate) ? 0x09u : 0x00u;
            if(InterruptTimer.IsInRealtimeInterval(HorizontalBlankingTime, HorizontalPeriod))
                value |= 0x01u;

            return (byte)value;
        }
        #endregion

        #region Private Fields
        private readonly VirtualMachine vm;
        private readonly TextConsole textDisplay;
        private readonly Dac dac = new Dac();
        private readonly Graphics graphics;
        private GraphicsRegister graphicsRegister;
        private readonly Sequencer sequencer;
        private SequencerRegister sequencerRegister;
        private readonly AttributeController attributeController = new AttributeController();
        private AttributeControllerRegister attributeRegister;
        private readonly CrtController crtController = new CrtController();
        private CrtControllerRegister crtRegister;
        private readonly IntPtr videoRam;
        private readonly NativeMemory nativeMemory;
        private readonly NativeHeap videoHeap = new NativeHeap(4096);
        private bool attributeDataMode;
        private bool defaultPaletteLoading = true;
        private int verticalTextResolution = 16;
        private readonly Vesa.VbeHandler vbe;
        #endregion

        #region Private Static Fields
        private static readonly long RefreshRate = (long)((1000.0 / 60.0) * InterruptTimer.StopwatchTicksPerMillisecond);
        private static readonly long VerticalBlankingTime = RefreshRate / 40;
        private static readonly long HorizontalPeriod = (long)((1000.0 / 60.0) / 480.0 * InterruptTimer.StopwatchTicksPerMillisecond);
        private static readonly long HorizontalBlankingTime = HorizontalPeriod / 2;
        #endregion

        #region Public Constants
        /// <summary>
        /// Total number of bytes allocated for video RAM.
        /// </summary>
        public const int TotalVramBytes = 1024 * 1024;
        /// <summary>
        /// Segment of the VGA static functionality table.
        /// </summary>
        public const ushort StaticFunctionalityTableSegment = 0x0100;
        #endregion
    }
}
